/*!
 * \file dmxcon/fixture.c
 * \author Christian Spoo
 * \date 03.08.2010
 *
 * \brief DMXcon fixture management
 * 
 * The fixture API provides means to create and manage the connected
 * DMX fixtures.
 */

#include <errno.h>
#include <malloc.h>
#include <string.h>

#include <dmxcon/fixture.h>

struct fixture_t *fixture_create(unsigned int start_addr, unsigned int num_channels, const char *vendor, const char *product, const char *name) {
  struct fixture_t *fixture = (struct fixture_t *)malloc(sizeof(struct fixture_t));
  if (fixture) {
    memset(fixture, 0, sizeof(struct fixture_t));

    fixture->start_addr = start_addr;
    fixture->num_channels = num_channels;
    fixture->name = strdup(name);
    fixture->vendor = strdup(vendor);
    fixture->product = strdup(product);

    fixture->data = (unsigned char *)malloc(fixture->num_channels * sizeof(unsigned char));
    fixture->channels = NULL;
    memset(fixture->data, 0, fixture->num_channels * sizeof(unsigned char));
  }

  return fixture;
}

int fixture_destroy(struct fixture_t *fixture) {
  if (fixture) {
    struct fixture_channel_t *channel = fixture->channels;

    /* Free channel list */
    if (channel) {
      while (channel) {
        struct fixture_channel_t *next = channel->next;
        if (channel->name)
          free(channel->name);

        free(channel);
        channel = next;
      }
    }

    /* Free data array */
    if (fixture->data)
      free(fixture->data);

    /* Free name */
    if (fixture->name)
      free(fixture->name);
    if (fixture->vendor)
      free(fixture->vendor);
    if (fixture->product)
      free(fixture->product);

    /* Free descriptor structure */
    free(fixture);
    return 0;
  }

  errno = EINVAL;
  return -1;
}

struct fixture_channel_t *fixture_add_channel(struct fixture_t *fixture, unsigned short offset, unsigned int type, const char *name, unsigned short width, unsigned char endianness, unsigned char fading) {
  if (fixture) {
    struct fixture_channel_t *channel = (struct fixture_channel_t *)malloc(sizeof(struct fixture_channel_t));
    struct fixture_channel_t *p = fixture->channels;
    unsigned short min = 0, max = 511;
    
    channel->type = type;
    channel->name = strdup(name);
    channel->width = width;
    channel->endianness = endianness;
    channel->fading = fading;

    channel->next = NULL;
    channel->offset = offset;

    if (offset + channel->width > fixture->num_channels) { 
      free(channel->name);
      free(channel);

      errno = EINVAL;
      return NULL;
    }

    if (p) {
      while (p) {
        if ((p->offset < offset) && (p->offset+p->width-1 > min))
          min = p->offset+p->width-1;

        if ((p->offset > offset) && (p->offset < max))
          max = p->offset;

        p = p->next;
      }

      /*
      printf("channel: %s\n", name);
      printf("min: %u offset: %u max: %u\n", min, offset, max);
      */

      if ((min < offset) && (max > offset+width-1)) {
        /* Determine the position at which to insert the new channel */
        p = fixture->channels;
        while (p) {
          if (p->next) {
            if ((p->offset < offset) && (p->next->offset > offset)) {
              channel->next = p->next;
              break;
            }
          }
          else
            break;

          p = p->next;
        }

        p->next = channel;
      }
      else {
        free(channel->name);
        free(channel);

        errno = ENOMEM;
        return NULL;
      }
    }
    else
      fixture->channels = channel;

    return channel;
  }

  errno = EINVAL;
  return NULL;
}

int fixture_remove_channel(struct fixture_t *fixture, struct fixture_channel_t *channel) {
  if (fixture) {

    return 0;
  }

  errno = EINVAL;
  return -1;
}

int fixture_list_channels(struct fixture_t *fixture) {
  if (fixture) {
    struct fixture_channel_t *channel = fixture->channels;

    printf("+-----------------------------------------------------------------+\n");
    printf("| Channel list                                                    |\n");
    printf("+-----------------------------------------------------------------+\n");
    printf("| Fixture: %-54.54s |\n", fixture->name);
    printf("| Vendor: %-22.22s Product: %-23.23s |\n", fixture->vendor, fixture->product);
    printf("+------+----------------------+-------+-------+-----+-----+-------+\n");
    printf("|  Off | Name                 | Type  | Width | End | Fad | Value |\n");
    printf("+------+----------------------+-------+-------+-----+-----+-------+\n");

    while (channel) {
      char fading[11];
      if (channel->fading == DMX_CHANNEL_FADING)
        strcpy(fading, "Fading");
      else
        strcpy(fading, "Non-fading");

      printf("|  %-3u | %-20.20s | %-5.5s | %-5u | %-3.3s | %-3.3s | %-3u   |\n", channel->offset, channel->name, channel_name_from_type(channel->type), channel->width, channel_endianness_from_int(channel->endianness), fading, fixture->data[channel->offset]);
      channel = channel->next;
    }
    printf("+------+----------------------+-------+-------+-----+-----+-------+\n");

    return 0;
  }

  errno = EINVAL;
  return -1;
}

char *channel_name_from_type(unsigned int type) {
  switch(type) {
    case DMX_CHANNEL_MODE:
      return "Mode";
    case DMX_CHANNEL_PAN:
      return "Pan";
    case DMX_CHANNEL_TILT:
      return "Tilt";
    case DMX_CHANNEL_RED:
      return "Red";
    case DMX_CHANNEL_GREEN:
      return "Green";
    case DMX_CHANNEL_BLUE:
      return "Blue";
    case DMX_CHANNEL_STROBE:
      return "Strobe";
    case DMX_CHANNEL_PROGRAM:
      return "Program";
    default:
      return "Unknown";
  }
}

char *channel_endianness_from_int(unsigned char endianness) {
  if (endianness == DMX_CHANNEL_BIG_ENDIAN)
    return "BE";

  return "LE";
}

int fixture_set_channel(struct fixture_t *fixture, unsigned int type, unsigned short index, unsigned long long value) {
  if (fixture) {
    struct fixture_channel_t *p = fixture->channels;
    unsigned int n = 0;
    unsigned char *data = NULL;

    /* Is fixture registered? */
    if (!fixture->stage) {
      errno = EAGAIN;
      return -1;
    }

    /* Try to find the index-th occurrence of a channel with the specified type */
    while (p) {
      if (p->type == type)
        if (index == n++)
          break;

      p = p->next;
    }

    /* Do we have found a channel? */
    if (!p) {
      errno = ENOENT;
      return -1;
    }

    /* Check whether channel width is larger than value type (unsigned long) */
    if (p->width > (sizeof(unsigned long long) / sizeof(unsigned char))) {
      errno = EFBIG;
      return -1;
    }

    /* Store channel value */
    data = (unsigned char *)malloc(p->width * sizeof(unsigned char));
    if (!data)
      return -1;
    
    if (p->endianness == DMX_CHANNEL_BIG_ENDIAN) {
      for (n = 0; n < p->width; n++) {
        data[p->width - n - 1] = (unsigned char)(value & 0xFF);
        value >>= 8;
      }
    }
    else {
      for (n = 0; n < p->width; n++) {
        data[n] = (unsigned char)(value & 0xFF);
        value >>= 8;
      }
    }

    stage_set_channels(fixture->stage, fixture->start_addr + p->offset, data, p->width);

    free(data);
  }

  errno = EINVAL;
  return -1;
}

